/*
 * Copyright 2012 Niklas Rehfeld
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.madpie.converters;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Scanner;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.xml.XMLConstants;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;
import javax.xml.validation.Schema;
import javax.xml.validation.SchemaFactory;
import org.madpie.schemas.ComponentType;
import org.madpie.schemas.Machine;
import org.madpie.schemas.ProcessorType;
import org.madpie.util.SchemaUtils;
import org.xml.sax.SAXException;

/**
 * Parses The output from lscpu and outputs the important information to an xml
 * file of the hw.xsd format.
 *
 * TODO get/set the location of the xml schemas in an external file. 
 * 
 * @author Niklas Rehfeld
 */
public class Lscpu
{

    private Machine machine;
    private ProcessorType cpu;
    private Schema hwSchema;
//    private JAXBContext jbc;

    /**
     * @param args input output
     */
    public static void main(String[] args) throws SAXException
    {

        Logger.getLogger(Lscpu.class.getName()).setLevel(Level.ALL);
        if (args.length < 2)
        {
            printUsage();
        }

        InputStream is;
        OutputStream os;
        Machine m;
        Lscpu l;
        try
        {
            if (args[0].equals("-"))
            {
                is = System.in;
            }
            else
            {
                File fin = new File(args[0]);
                is = new FileInputStream(fin);
            }

            if (args[1].equals("-"))
            {
                os = System.out;
                m = SchemaUtils.ReadHardwareFile(null);
            }
            else
            {
                /* NOTE: the order of these lines is important. If we open the 
                 * FileOutputStream before reading in the hardware file, 
                 * reading the file fails, as the Unmarshaller tries to open a 
                 * stream on the same file and will see nothing. */
                File fout = new File(args[1]);
                m = SchemaUtils.ReadHardwareFile(fout);
                os = new FileOutputStream(fout);
            }
            l = new Lscpu(m);
            l.readInfo(is);
            l.writeXMLFile(os);

        } catch (FileNotFoundException ex)
        {
            Logger.getLogger(Lscpu.class.getName()).log(Level.SEVERE, null, ex);
            return;
        } catch (JAXBException ex)
        {
            Logger.getLogger(Lscpu.class.getName()).log(Level.SEVERE,
                                                        "Something went wrong, ",
                                                        ex);
            return;
        } catch (IOException ex)
        {
            Logger.getLogger(Lscpu.class.getName()).log(Level.SEVERE, null, ex);
            return;
        }

    }

    /**
     * 
     * @throws JAXBException
     */
    public Lscpu(Machine m) throws JAXBException, SAXException
    {
//        jbc = JAXBContext.newInstance("org.madpie.schemas");
        machine = m;
        cpu = m.getProcessor();
        if (cpu == null)
        {
            cpu = new ProcessorType();
        }
        hwSchema = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI)
                .newSchema(new File("source/xml/hw.xsd"));
    }

    /**
     * Parses the output of lscpu and populates the &lt;Processor&gt; element in 
     * the current machine with the values from that output.
     *
     * @param in output from lscpu.
     */
    public void readInfo(InputStream in)
    {
        if (in == null)
        {
            throw new IllegalArgumentException("Input stream is null.");
        }
        Scanner s = new Scanner(in);
        String[] line = new String[2];
        while (s.hasNextLine())
        {
            line = s.nextLine().split(":");
            if (line[0] == null || line[1] == null)
            {
                continue;
            }
            if (line[0].equalsIgnoreCase("Architecture"))
            {
                cpu.setArchitecture(line[1].trim());
                Logger.getLogger(Lscpu.class.getName()).log(Level.INFO,
                                                            "Arch: {0}", line[1]);
            }
            else if (line[0].equalsIgnoreCase("Vendor ID"))
            {
                cpu.setVendor(line[1].trim());
                Logger.getLogger(Lscpu.class.getName()).log(Level.INFO,
                                                            "Vendor: {0}",
                                                            line[1]);
            }
            else if (line[0].equalsIgnoreCase("Model"))
            {
                cpu.setModel(Short.parseShort(line[1].trim()));
                Logger.getLogger(Lscpu.class.getName()).log(Level.INFO,
                                                            "Model: {0}",
                                                            line[1]);
            }
            else if (line[0].equalsIgnoreCase("Stepping"))
            {
                cpu.setStepping(Short.parseShort(line[1].trim()));
            }
            else if (line[0].equalsIgnoreCase("CPU Family"))
            {
                cpu.setFamily(Short.parseShort(line[1].trim()));
            }
            else if (line[0].equalsIgnoreCase("CPU MHz"))
            {
                cpu.setFrequency(Float.parseFloat(line[1].trim()));
            }
            else if (line[0].equalsIgnoreCase("CPU(s)"))
            {
                cpu.setNumber(Short.parseShort(line[1].trim()));
            }
            else if (line[0].equalsIgnoreCase("Core(s) per socket"))
            {
                cpu.setCores(Short.parseShort(line[1].trim()));
            }
            else if (line[0].equalsIgnoreCase("CPU op-mode(s)"))
            {
                cpu.setOpModes(line[1].trim());
            }
            else if (line[0].equalsIgnoreCase("Virtualization"))
            {
                cpu.setVirtualisation(line[1].trim());
            }
            else if (line[0].equalsIgnoreCase("Byte Order"))
            {
                cpu.setByteOrder(line[1].trim());
            }
        }

    }

    /**
     * Opens an existing hardware file.
     *
     * @param f
     * @throws IOException
     * @deprecated use {@link SchemaUtils#ReadHardwareFile(java.io.File)}.
     */
    public boolean readExistingOutput(File f) throws IOException, SAXException
    {
        if (f == null)
        {
            throw new IllegalArgumentException("Output file is null.");
        }

        if (f.canRead())
        {
            Logger.getLogger(Lscpu.class.getName()).info("opening existing file");

            try
            {
                JAXBContext jbc = JAXBContext.newInstance(Machine.class);
                Logger.getLogger(Lscpu.class.getName()).log(Level.INFO,
                                                            "created context");
                Unmarshaller um = jbc.createUnmarshaller();
                Logger.getLogger(Lscpu.class.getName()).log(Level.INFO,
                                                            "created unmarshaller");
                um.setSchema(hwSchema);
                machine = (Machine) um.unmarshal(f);
                cpu = machine.getProcessor();
                return true;
            } catch (JAXBException ex)
            {
                Logger.getLogger(Lscpu.class.getName()).log(Level.SEVERE,
                                                            "Can't unmarshall the file: ",
                                                            ex);
            }
        }

        return false;

    }

    /**
     * Writes out the output (including the original information in the hardware
     * file) back to that hardware file (an XML document conforming to hw.xsd) .
     * @param os OutputStream to write to.
     * @throws JAXBException 
     */
    public void writeXMLFile(OutputStream os) throws JAXBException
    {
        machine.setProcessor(cpu);
        JAXBContext jbc = JAXBContext.newInstance(Machine.class);
        Marshaller m = jbc.createMarshaller();
        m.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
        m.setSchema(hwSchema);
        m.marshal(SchemaUtils.cleanMachine(machine), os);
    }

    /**
     * The usual printUsage thing. tells a user how to run the program.
     */
    public static void printUsage()
    {
        System.out.println("Usage:");
        System.out.println("\t lscpu2xml input output");
        System.out.println(
                "\t where input can be a file or '-' for standard input");
    }

    /**
     * Mainly used for debugging. 
     * @return A String containing the names of the machine, all of the components 
     * and the processor.
     */
    public String machineState()
    {
        String s = machine.getName() + "\n";
        for (ComponentType c : machine.getComponent())
        {
            s += c.getName() + "\n";
        }
        s += machine.getProcessor().getName();

        return s;
    }
}
